package tabletask

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"time"

	"cvevulner/cve-timed-task/db_models"
	"cvevulner/cve-timed-task/util"
	"cvevulner/models"

	"github.com/astaxie/beego/logs"
	"github.com/astaxie/beego/orm"
)

//var cveListDate []db_models.VulnCenter

type Compare struct {
	CveId  string
	CveNum string
}

// QueryCveALLData Find cve with missing necessary fields
func QueryCveALLData(days int, ormModel orm.Ormer) []models.VulnCenter {
	now := time.Now()
	beforeDate := time.Date(now.Year(), now.Month(), now.Day()-days,
		0, 0, 0, 0, now.Location()).Format("2006-01-02 15:04:05")

	cveIdList := make([]Compare, 0, 100)
	scoreList, rowsAffected, err := db_models.GetCveScoreListByBeforeDate(beforeDate, ormModel)
	if err == nil {
		if rowsAffected > 0 {
			for _, v := range scoreList {
				cveIdList = append(cveIdList, Compare{
					CveId:  strconv.FormatInt(v.CveId, 10),
					CveNum: v.CveNum,
				})
			}
		}
	} else {
		logs.Error("db_models.GetCveScoreList error:", err.Error())
	}
	vulnCenterList, rowsAffected, err := db_models.GetCveVulnCenterList("", "", beforeDate, ormModel)
	if err == nil {
		if rowsAffected > 0 {
			for _, v := range vulnCenterList {
				cveIdList = append(cveIdList, Compare{
					CveId:  strconv.FormatInt(v.CveId, 10),
					CveNum: v.CveNum,
				})
			}
		}
	} else {
		logs.Error("db_models.GetCveVulnCenter error:", err.Error())
	}

	issueTemplateList, rowsAffected, err := db_models.GetCveIssueTemplate("0", "", "", beforeDate, ormModel)
	if err == nil {
		if rowsAffected > 0 {
			for _, v := range issueTemplateList {
				cveIdList = append(cveIdList, Compare{
					CveId:  strconv.FormatInt(v.CveId, 10),
					CveNum: v.CveNum,
				})
			}
		}
	} else {
		logs.Error("db_models.GetCveIssueTemplate error:", err.Error())
	}

	cveIdList = RemoveRepeatedElement(cveIdList)
	cveList := make([]models.VulnCenter, 0, len(cveIdList))
	if len(cveIdList) > 0 {
		for _, v := range cveIdList {
			vulnCenter, err := db_models.GetOneVulnCenter(v.CveId, v.CveNum, ormModel)
			if err != nil {
				logs.Error("db_models.GetOneVulnCenter error:", err.Error())
				continue
			}

			cveList = append(cveList, vulnCenter)
		}
	}
	return cveList
}

func RemoveRepeatedElement(arr []Compare) (newArr []Compare) {
	newArr = make([]Compare, 0)
	for i := 0; i < len(arr); i++ {
		repeat := false
		for j := i + 1; j < len(arr); j++ {
			if arr[i] == arr[j] {
				repeat = true
				break
			}
		}
		if !repeat {
			newArr = append(newArr, arr[i])
		}
	}
	return
}

func GetRepair(cve string) string {
	var rep string = ""
	resp, err := http.Get(fmt.Sprintf(CVEurl, cve))
	if err != nil {
		logs.Error("get repair time of %s error: %s", cve, err.Error())

		return ""
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil || body == nil {
		return ""
	}
	var res CVE
	err = json.Unmarshal(body, &res)
	if err == nil {
		if res.Vulnerabilities != nil && len(res.Vulnerabilities) > 0 && len(res.Vulnerabilities[0].Cve.Published) >= 16 {
			w, timeerr := time.Parse("2006-01-02T15:04", res.Vulnerabilities[0].Cve.Published[:16])
			if timeerr == nil {
				rep = w.Add(8 * time.Hour).Format("2006-01-02 15:04")
			}
		}
	}
	return rep
}

// UpdateCveVuln update data
func UpdateCveVuln(url string, cve models.VulnCenter, ormModel orm.Ormer) error {
	cveNum := strings.TrimSpace(cve.CveNum)
	cveId := cve.CveId
	cveStatus := cve.Status
	cveDesc := strings.TrimSpace(cve.Description)
	repairTime := strings.TrimSpace(cve.RepairTime)
	updateTime := time.Now()

	cveStatusx := cveStatus
	comp := make([]interface{}, 0, 10)
	comp = append(comp, 0, 3, 4, 5, 6, 7, 8)
	if util.InSlice(comp, int(cveStatus)) {
		cveStatusx = 0
	}
	compTwo := make([]interface{}, 0, 10)
	compTwo = append(compTwo, 1, 2)
	if util.InSlice(compTwo, int(cveStatus)) {
		cveStatusx = 1
	}
	listx, err := Crawling(url)
	if err != nil {
		logs.Error("crawling data error:", err, url)
		return err
	}
	rep := GetRepair(cve.CveNum)
	if len(rep) > 0 {
		listx.RepairTime = rep
	}

	if (listx.CveDesc == "" || len(listx.CveDesc) < 2) &&
		(listx.NvdScore == "") && (listx.RepairTime == "" || len(listx.RepairTime) > 2) {
		logs.Info("The data does not exist and will not be processed temporarily", listx)
		return errors.New("The data does not exist and will not be processed temporarily")
	}

	isRepairTimeCorrect := func(t string) bool {
		//10 means type 2023-07-11
		if t == "" || len(t) == 10 {
			return false
		}
		// 2023-07-11 00:00 is incorrect
		if len(t) == 16 && t[11:] == "00:00" {
			return false
		}

		return true
	}

	if !isRepairTimeCorrect(repairTime) && rep != "" {
		err = db_models.UpdateVulnCenterTypeOne(models.VulnCenter{
			RepairTime: rep,
			UpdateTime: updateTime,
			Status:     cveStatusx,
			CveId:      cveId,
		}, ormModel)
		if err != nil {
			logs.Error("db_models.UpdateVulnCenterTypeOne error:", err)
			return err
		}
	}

	if listx.CveDesc != "" && len(listx.CveDesc) > 2 && cveDesc != listx.CveDesc {
		err = db_models.UpdateVulnCenterTypeTwo(models.VulnCenter{
			Description: listx.CveDesc,
			UpdateTime:  updateTime,
			Status:      cveStatusx,
			CveId:       cveId,
		}, ormModel)
		if err != nil {
			logs.Error("db_models.UpdateVulnCenterTypeTwo error:", err)
			return err
		}
	}

	nvdScore, _ := strconv.ParseFloat(listx.NvdScore, 64)
	if nvdScore > 0 && listx.NvdScore != "" {
		var scoreType string
		if listx.ScoreType == "v3.0" {
			scoreType = "v3"
		} else {
			scoreType = "v2"
		}
		score, err := db_models.GetCveScoreByCveIdAndCveNum(cveId, cveNum, ormModel)
		if err != nil {
			if errors.Is(err, orm.ErrNoRows) {
				logs.Info(err)
			} else {
				logs.Error("db_models.GetCveScoreByCveIdAndCveNum error:", err)
				return err
			}
		} else {
			if score.NVDScore != nvdScore {
				err := db_models.UpdateCveScore(models.Score{
					NVDScore:           nvdScore,
					NvectorVule:        listx.VectorValue,
					NattackVector:      listx.AttackVector,
					NaccessVector:      listx.AccessVector,
					NattackComplexity:  listx.AttackComplexity,
					NaccessComplexity:  listx.AccessComplexity,
					NprivilegeRequired: listx.PrivilegeRequired,
					NuserInteraction:   listx.UserInteraction,
					Nscope:             listx.Scope,
					Nconfidentiality:   listx.Confidentiality,
					Nintegrity:         listx.Integrity,
					Navailability:      listx.Availability,
					Nauthentication:    listx.Authentication,
					UpdateTime:         updateTime,
					ScoreType:          scoreType,
					Id:                 score.Id,
				}, ormModel)
				if err != nil {
					logs.Error("db_models.UpdateCveScore error:", err)
					return err
				}
			}
		}
	}

	templateResult, err := db_models.GetIssueTemplateTypeOne(models.IssueTemplate{
		CveId:  cveId,
		CveNum: cveNum,
	}, ormModel)
	if err != nil {
		logs.Error(" db_models.GetIssueTemplateTypeOne error:", err)
		return nil
	}

	flag := false
	if listx.NvdScore != "" && nvdScore > 0 && templateResult.NVDScore != nvdScore {
		err = db_models.UpdateCveIssueTemplateTypeOne(models.IssueTemplate{
			NVDScore:   nvdScore,
			UpdateTime: updateTime,
			TemplateId: templateResult.TemplateId,
		}, ormModel)
		if err != nil {
			logs.Error("db_models.UpdateCveIssueTemplateTypeOne error:", err)
			return err
		}
		flag = true
	}

	if (listx.VectorValue != "" && len(listx.VectorValue) > 2) &&
		(templateResult.NVDVector != listx.VectorValue) {
		err = db_models.UpdateCveIssueTemplateTypeTwo(models.IssueTemplate{
			NVDVector:  listx.VectorValue,
			UpdateTime: updateTime,
			TemplateId: templateResult.TemplateId,
		}, ormModel)
		if err != nil {
			logs.Error("db_models.UpdateCveIssueTemplateTypeTwo error:", err)
			return err
		}
		flag = true
	}

	if (listx.CveDesc != "" && len(listx.CveDesc) > 2) &&
		(templateResult.CveBrief != listx.CveDesc) {
		err = db_models.UpdateCveIssueTemplateTypeThree(models.IssueTemplate{
			CveBrief:   listx.CveDesc,
			UpdateTime: updateTime,
			TemplateId: templateResult.TemplateId,
		}, ormModel)
		if err != nil {
			logs.Error("db_models.UpdateCveIssueTemplateTypeThree error:", err)
			return err
		}
		flag = true
	}

	if flag {
		err = db_models.UpdateVulnCenter(models.VulnCenter{
			UpdateTime: updateTime,
			Status:     cveStatusx,
			CveId:      cveId,
		}, ormModel)
		if err != nil {
			logs.Error("db_models.UpdateVulnCenter error:", err)
			return err
		}
	}
	return nil
}

// SupplementCve
//1. Find cve with missing necessary fields;
//2. Go to the CVE official website to find the CVE information;
//3. Fill in the corresponding table again;
func SupplementCve() {
	ormModel := orm.NewOrm()
	cveList := QueryCveALLData(3, ormModel)

	if len(cveList) > 0 {
		for _, cve := range cveList {
			cveNum := strings.TrimSpace(cve.CveNum)
			cveVersion := strings.TrimSpace(cve.CveVersion)
			packName := strings.TrimSpace(cve.PackName)
			url := fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", cveNum)
			_ = UpdateCveVuln(url, cve, ormModel)
			resultSpecError, err := db_models.GetSpceError(cveNum, "src-openEuler", packName, ormModel)
			if err != nil && !errors.Is(orm.ErrNoRows, err) {
				logs.Error("db_models.GetSpceError error:", err)
				continue
			}
			err = db_models.UpdateCveOriginExcel(models.OriginExcel{
				CveDesc:    resultSpecError.Description,
				CveStatus:  6,
				CveNum:     cveNum,
				PackName:   packName,
				CveVersion: cveVersion,
			}, ormModel)
			if err != nil {
				logs.Error("db_models.UpdateCveOriginExcel error:", err)
			}
		}
	}
}

// LongSupplementCve 1. Find cve with missing necessary fields;
//2. Go to the CVE official website to find the CVE information;
//3. Fill in the corresponding table again;
func LongSupplementCve() {
	ormModel := orm.NewOrm()
	cveList := QueryCveALLData(365, ormModel)
	if len(cveList) > 0 {
		for _, cve := range cveList {
			cveNum := strings.TrimSpace(cve.CveNum)
			cveVersion := strings.TrimSpace(cve.CveVersion)
			packName := strings.TrimSpace(cve.PackName)
			url := fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", cveNum)
			_ = UpdateCveVuln(url, cve, ormModel)
			resultSpecError, err := db_models.GetSpceError(cveNum, "src-openEuler", packName, ormModel)
			if err != nil {
				logs.Error("db_models.GetSpceError error:", err)
				continue
			}
			logs.Info("Filter, modify the status to 6", cveNum)
			err = db_models.UpdateCveOriginExcel(models.OriginExcel{
				CveDesc:    resultSpecError.Description,
				CveStatus:  6,
				CveNum:     cveNum,
				PackName:   packName,
				CveVersion: cveVersion,
			}, ormModel)
			if err != nil {
				logs.Error("db_models.UpdateCveOriginExcel error:", err)
			}
		}
	}
}

func PullCve(cveNum string) (resp int) {
	ormModel := orm.NewOrm()
	resp = 0
	cveList, rowsAffected, err := db_models.QueryCveByNumber(cveNum, ormModel)
	if err != nil {
		return resp
	}
	if rowsAffected == 0 {
		return resp
	}
	fmt.Println(rowsAffected)

	if len(cveList) > 0 {
		for _, cve := range cveList {
			cveNum := strings.TrimSpace(cve.CveNum)
			cveVersion := strings.TrimSpace(cve.CveVersion)
			packName := strings.TrimSpace(cve.PackName)
			url := fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", cveNum)
			err = UpdateCveVuln(url, cve, ormModel)
			if err == nil {
				resp = 1
			}
			resultSpecError, err := db_models.GetSpceError(cveNum, "src-openEuler", packName, ormModel)
			if err != nil {
				logs.Error("db_models.GetSpceError error:", err)
			}
			logs.Info("Filter, modify the status to 6", cveNum)
			err = db_models.UpdateCveOriginExcel(models.OriginExcel{
				CveDesc:    resultSpecError.Description,
				CveStatus:  6,
				CveNum:     cveNum,
				PackName:   packName,
				CveVersion: cveVersion,
			}, ormModel)
			if err != nil {
				logs.Error("db_models.UpdateCveOriginExcel error:", err)
			}
		}
	}
	return resp
}
